 /**
 * @fileoverview JXON - A utility for converting an XML document to a JavaScript object.
 * @link 
 * @author Robert Wilson, Italy, July 2014
 * @version 0.82
 */

/* ***** BEGIN LICENSE BLOCK *****
*	The MIT License (MIT)
*
*	Copyright (c) 2014 JXON
*
*	Permission is hereby granted, free of charge, to any person obtaining a copy
*	of this software and associated documentation files (the "Software"), to deal
*	in the Software without restriction, including without limitation the rights
*	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*	copies of the Software, and to permit persons to whom the Software is
*	furnished to do so, subject to the following conditions:
*
*	The above copyright notice and this permission notice shall be included in all
*	copies or substantial portions of the Software.
*
*	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
*	SOFTWARE. * ***** END LICENSE BLOCK ***** */
*
/* ***** BEGIN DOCUMENTATION BLOCK *****
 *
 *  This code is based on concepts and examples from Mozilla Developer Network
 *  https://developer.mozilla.org/en-US/docs/JXON
 *
 *  In most cases the basic syntax is all that is required.
 *
 *  Example:
 *	var obj = jxon( xml );		
 *
 *  The xml parameter can be either an XMLHttpRequest object, XMLDocument,
 *	or even an XML string.
 *
 *	Examples:
 *  var obj1 = jxon( xhr ), obj2 = jxon( xhr.responseXML );  // same result
 *
 *  You may optionally specify an xml node name to return. With large xml data 
 * 	files this can improve conversion speed and return an object that is easier
 *  to work with. The node name must match what appears in the xml file. If the
 *	node name is incorrect or doesn't exist then the function will return null.
 *
 *	Example:
 *  var rss = jxon( xml, 'item' );	// RSS data feed
 *
 *	The node name may also include an xml namesapce.
 *
 *  Example:
 *	var list = jxon( xml, 'z:row' ); 	// SharePoint SOAP ListItems
 *
 *	IMPORTANT:
 *	When a node name is supplied the function will always return an array, even
 *	when there are less than two nodes. This is done to return a consistent object
 *	regardless of what data is converted. If the data is known to always return
 *	a single node then consider appending an array index:
 *	Example:
 *	var obj = jxon( xml, 'item')[0];
 *	
 *  Additional options may be supplied to handle special use cases.
 *  
 *	Example:
 *	var obj = jxon( xml, options);	
 *
 *	"options" is an object with the following possible keys:
 *
 *	options.find	(String)	The xml node name to return (see above)
 *	options.xmlns	(String)	The namespace URL of the find parameter
 *	options.prefix	(String)	A prefix to prepend to attributes.
 *  options.escape  (Boolean)	Convert node names to JavaScript friendly names. 
 *
 *  The "xmlns" is only useful when the node name has a namespace. In practice it is 
 *  rarely required as the default wildcard method works in most cases. Use a non-IE
 *  browser for testing xmlns values as IE ignores the parameter.
 *  
 *	The "escape" switch defaults to true, which enables the node and attribute name
 *	converter. The converter removes special characters, prefixes, and escaped 
 *	unicode codes for names. These names are used in the returned object and are
 *	often easier to work with in JavaScript code. However, there might be applications
 *	that require raw names as found in the XML. Setting the "escape" option to false
 *	will turn off the converter and might also yield improved speed with large data sets.  
 * 
 *  Example:
 *	var obj = jxon( xml, {
 *		find: 'rs:data',
 *		xmlns: 'urn:schemas-microsoft-com:rowset',
 *		prefix: '$',
 *		escape: true
 *	});
 *
 * 	The JSON2.js library (not provided) can be useful in debugging.
 *	Example:
 *	console.info( JSON.stringify( jxon(xml), null, '  '));
 *
 * ***** END DOCUMENTATION BLOCK ***** */

 
/**
 * Transforms the xml document into a JavaScript object
 * @param {Document} xml The XML document to be transformed.
 * @param {Object} options Optional. An object with "find","xmlns","prefix",and "convert" properties.
 * @type Object
 */


var jxon = (function( ) {
	
	// PUBLIC METHODS
		
	function toJavaScript(xml, options) {
	
		var i, name, nodes,	obj={}, buffer=[];
	
		// handle various input types: xml, xhr, string
		if (!xml.nodeType) {
			if (typeof xml=='string') xml = toXML( xml );
			if (xml && xml.responseText && !xml.responseXML) xml = toXML( xml.responseText );
			if (xml && xml.responseXML) xml = xml.responseXML;
			if (!xml || !xml.nodeType) throw new Error('xml document not found');
		}
		
		switch( xml.nodeType ) {
		
			case DOCUMENT_NODE:
			
				options = config( options );
					
				nodes = findNodes( xml, options.find, options.xmlns );
	   			//if (options.find  && !nodes.length) throw new Error('unabled to find xml node \"' + name + '\" xmlns:' + options.xmlns); 
    			
				// start iteration	
				for(i=0; i<nodes.length; i++) {
					obj = toJavaScript( nodes[i], options );
					if (obj) buffer.push( obj );
				}
				//obj = (buffer.length && buffer.length==1)? buffer[0]: buffer;
				//obj = (options.find)? buffer: buffer[0];
				obj = buffer;  // <-- always return an array even when just 1 item
			break;
			
			case ELEMENT_NODE:
				// node without attributes and single child node
				var o = xml.childNodes;
				if (xml.attributes.length==0 
					&& o.length==1  
					&& o.item(0).nodeValue
				) obj = o.item(0).nodeValue; 

				// node with attributes
				o = xml.attributes;
				for(i=0; i<o.length; i++) {
					name = options.prefix + escapeName( o.item(i).nodeName, options);
					// attribute.nodeValue is deprecated in DOM 4
					obj[name] = o.item(i).value || o.item(i).nodeValue; 
				}
				// node with child nodes
				o = xml.childNodes;
				for(i=0; i<o.length; i++) {
					if (o.item(i).nodeType!=TEXT_NODE) { // <-- prevents extranous text CRLF nodes 
						name = escapeName( o.item(i).nodeName, options );
						if (typeof obj[name]=='undefined') {
							obj[name] =  toJavaScript( o.item(i), options );
						}
						else {
							// convert to array when nodes have same name
							if (typeof obj[name].push=='undefined') obj[name] = [obj[name]];
							obj[name].push(  toJavaScript( o.item(i), options ));
						}
					}
				}
			break;
					
			case DOCUMENT_TYPE_NODE:
			case CDATA_SECTION_NODE:
			case TEXT_NODE:
					obj = xml.nodeValue;			
			break;
			/*		
			case CDATA_SECTION_NODE:
				obj = '<![CDATA[' + xml.nodeValue + ']]>';			
			break;
			*/
			case COMMENT_NODE:
				obj = (xml.nodeValue || '').substr(0,255).replace(/</g,'&lt;').replace(/>/g,'&gt;');
			break;
			
		
			default:
				obj = null;
		}

		return obj;
	}

	


	// PRIVATE METHODS
	
	var ELEMENT_NODE=1,TEXT_NODE=3,CDATA_SECTION_NODE=4,COMMENT_NODE=8,DOCUMENT_NODE=9,DOCUMENT_TYPE_NODE=10;

	// Cross browser node finder with namespace support
	function findNodes( xml, name, xmlns ) {
		return (!name)? xml.childNodes: 
			(xml.getElementsByTagNameNS) ? xml.getElementsByTagNameNS( xmlns || '*', name.split(':').pop() ) 
   			: xml.getElementsByTagName( name ); // IE Quirks Mode
	}

	// Converts string to an XML document
	function toXML( text ) {
		var parser, xml = null;
		if (window.DOMParser) {
			parser = new DOMParser();
			xml = parser.parseFromString( text, 'text/xml' );
			if (xml.getElementsByTagName('parseerror').length>0) throw new Error('Syntax Error');
			return xml;
		}
		else {
			xml = new ActiveXObject( 'Microsoft.XMLDOM' );
			xml.async = false;
			xml.loadXML( text );
			return xml;
		}
	}

	// Converts an XML node name to a JavaScript friendly var name
	function escapeName( name, options ) {
		if (options.escape) {
			name = name.split(':').pop().replace(/[^a-z,0-9,_,\$]/ig,''); 	// Generic
			name = name.replace(/^ows_/i,'').replace(/_x\w{4}_/ig,'_'); 	// SharePoint
		}
		return name; 
	}
	
	// Sets configuration options	
	function config( options ) {		
		if (!options) options = {};
		if (typeof options=='string') options = {find: options};
		options.find = options.find || '';
		options.xmlns = options.xmlns || '*';
		options.prefix = options.prefix || '';
		options.escape = (typeof options.escape=='undefined' || options.escape)? true: false;
		return options;
	}

	return toJavaScript;

				
})()
